package com.yinxing.framework.system;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.yinxing.framework.utils.ArithUtils;
import com.yinxing.framework.utils.IpUtils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import oshi.hardware.CentralProcessor;
import oshi.hardware.CentralProcessor.TickType;
import oshi.hardware.GlobalMemory;
import oshi.hardware.HardwareAbstractionLayer;
import oshi.software.os.FileSystem;
import oshi.software.os.OSFileStore;
import oshi.software.os.OperatingSystem;
import oshi.util.Util;

import java.lang.management.ManagementFactory;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * 服务器相关信息
 */
@Slf4j
@Data
public class SystemInfo {

    private static final int OSHI_WAIT_SECOND = 1000;

    /**
     * 缓存key
     */
    private static final String cacheKey = "SystemInfo";

    /**
     * CPU相关信息
     */
    private Cpu cpu = new Cpu();

    /**
     * CPU统计图
     */
    private List<NameData> cpuPie = new ArrayList<>();

    /**
     * 內存相关信息
     */
    private Mem mem = new Mem();

    /**
     * JVM相关信息
     */
    private Jvm jvm = new Jvm();

    /**
     * 服务器相关信息
     */
    private Sys sys = new Sys();

    /**
     * 磁盘相关信息
     */
    private List<SysFile> sysFiles = new LinkedList<>();

    /**
     * 缓存组件
     */
    private static LoadingCache<String, SystemInfo> cache;

    static {
        CacheLoader<String, SystemInfo> loader = new CacheLoader<>() {
            @Override
            public SystemInfo load(String key) {
                SystemInfo info = new SystemInfo();
                info.copyTo();
                return info;
            }
        };

        //缓存10秒
        SystemInfo.cache = CacheBuilder.newBuilder()
                .expireAfterWrite(5, TimeUnit.SECONDS)
                .maximumSize(1)
                .build(loader);
    }

    /**
     * 缓存中获取系统运行信息
     */
    public static SystemInfo getSystemInfo() {
        try {
            return cache.get(cacheKey);
        } catch (ExecutionException e) {
            log.warn("从缓存中获取系统运行信息{SystemInfo}失败:{{}}", e.getMessage());
        }
        return new SystemInfo().copyTo();
    }

    /**
     * 获取计算机运行信息
     */
    private SystemInfo copyTo() {
        oshi.SystemInfo si = new oshi.SystemInfo();
        HardwareAbstractionLayer hal = si.getHardware();
        setCpuInfo(hal.getProcessor());
        setMemInfo(hal.getMemory());
        setSysInfo();
        setJvmInfo();
        setSysFiles(si.getOperatingSystem());
        return this;
    }

    /**
     * 设置CPU信息
     */
    private void setCpuInfo(CentralProcessor processor) {
        // CPU信息
        long[] prevTicks = processor.getSystemCpuLoadTicks();
        Util.sleep(OSHI_WAIT_SECOND);
        long[] ticks = processor.getSystemCpuLoadTicks();
        long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()];
        long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()];
        long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()];
        long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()];
        long cSys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()];
        long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()];
        long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()];
        long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()];
        long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal;

        log.debug("----------------cpu信息----------------");
        log.debug("cpu核数:" + processor.getLogicalProcessorCount());
        log.debug("cpu系统使用率:" + new DecimalFormat("#.##%").format(cSys * 1.0 / totalCpu));
        log.debug("cpu用户使用率:" + new DecimalFormat("#.##%").format(user * 1.0 / totalCpu));
        log.debug("cpu当前等待率:" + new DecimalFormat("#.##%").format(iowait * 1.0 / totalCpu));
        log.debug("cpu当前使用率:" + new DecimalFormat("#.##%").format(1.0-(idle * 1.0 / totalCpu)));

        cpu.setCpuNum(processor.getLogicalProcessorCount());
        cpu.setTotalFree(new DecimalFormat("#.##%").format(idle * 1.0 / totalCpu));
        cpu.setSys(new DecimalFormat("#.##%").format(cSys * 1.0 / totalCpu));
        cpu.setUsed(new DecimalFormat("#.##%").format(user * 1.0 / totalCpu));
        cpu.setWait(new DecimalFormat("#.##%").format(iowait * 1.0 / totalCpu));

        cpuPie.add(new NameData(cpu.getTotalFree(), (int) (idle * 1.0 / totalCpu * 10000)));
        cpuPie.add(new NameData(cpu.getSys(), (int) (cSys * 1.0 / totalCpu * 10000)));
        cpuPie.add(new NameData(cpu.getUsed(), (int) (user * 1.0 / totalCpu * 10000)));
    }

    /**
     * 设置内存信息
     */
    private void setMemInfo(GlobalMemory memory) {
        long totalByte = memory.getTotal();
        long acaliableByte = memory.getAvailable();
        log.debug("总内存 = " + formatByte(totalByte));
        log.debug("使用" + formatByte(totalByte-acaliableByte));
        log.debug("剩余内存 = " + formatByte(acaliableByte));
        log.debug("使用率：" + new DecimalFormat("#.##%").format((totalByte-acaliableByte)*1.0/totalByte));
        mem.setTotal(formatByte(totalByte));
        mem.setUsed(formatByte(totalByte-acaliableByte));
        mem.setFree(formatByte(acaliableByte));
        mem.setUsedRate(new DecimalFormat("#.##%").format((totalByte-acaliableByte)*1.0/totalByte));
    }

    /**
     * 设置服务器信息
     */
    private void setSysInfo() {
        Properties props = System.getProperties();
        sys.setComputerName(IpUtils.getHostName());
        sys.setComputerIp(IpUtils.getHostIp());
        sys.setOsName(props.getProperty("os.name"));
        sys.setOsArch(props.getProperty("os.arch"));
        sys.setUserDir(props.getProperty("user.dir"));
    }

    /**
     * 设置Java虚拟机
     */
    private void setJvmInfo() {
        Properties props = System.getProperties();
        Runtime runtime = Runtime.getRuntime();
        //jvm总内存
        long jvmTotalMemoryByte = runtime.totalMemory();
        //jvm最大可申请
        long jvmMaxMoryByte = runtime.maxMemory();
        //空闲空间
        long freeMemoryByte = runtime.freeMemory();
        //jdk版本
        String jdkVersion = props.getProperty("java.version");

        String startTime = getStartTime();
        String runTime = getRunTime();

        //jdk路径
        String jdkHome = props.getProperty("java.home");
        log.debug("jvm可申请最大内存 = " + formatByte(jvmMaxMoryByte));
        log.debug("jvm内存总量 = " + formatByte(jvmTotalMemoryByte));
        log.debug("jvm已使用内存 = " + formatByte(jvmTotalMemoryByte-freeMemoryByte));
        log.debug("jvm剩余内存 = " + formatByte(freeMemoryByte));
        log.debug("jvm内存使用率 = " + new DecimalFormat("#.##%").format((jvmTotalMemoryByte-freeMemoryByte)*1.0/jvmTotalMemoryByte));
        log.debug("java版本 = " + jdkVersion);
        log.debug("jvm启动时间 = " + startTime);
        log.debug("Jvm运行时间 = " + runTime);

        jvm.setMax(formatByte(jvmMaxMoryByte));
        jvm.setTotal(formatByte(jvmTotalMemoryByte));
        jvm.setUse(formatByte(jvmTotalMemoryByte-freeMemoryByte));
        jvm.setFree(formatByte(freeMemoryByte));
        jvm.setUsedRate(new DecimalFormat("#.##%").format((jvmTotalMemoryByte-freeMemoryByte)*1.0/jvmTotalMemoryByte));
        jvm.setVersion(jdkVersion);
        jvm.setHome(props.getProperty("java.home"));
        jvm.setStartTime(startTime);
        jvm.setRunTime(runTime);
    }

    /**
     * 设置磁盘信息
     */
    private void setSysFiles(OperatingSystem os) {
        FileSystem fileSystem = os.getFileSystem();
        List<OSFileStore> fsArray = fileSystem.getFileStores();
        for (OSFileStore fs : fsArray) {
            long free = fs.getUsableSpace();
            long total = fs.getTotalSpace();
            long used = total - free;
            SysFile sysFile = new SysFile();
            sysFile.setDirName(fs.getMount());
            sysFile.setSysTypeName(fs.getType());
            sysFile.setTypeName(fs.getName());
            sysFile.setTotal(convertFileSize(total));
            sysFile.setFree(convertFileSize(free));
            sysFile.setUsed(convertFileSize(used));
            sysFile.setUsage(ArithUtils.mul(ArithUtils.div(used, total, 4), 100));
            sysFiles.add(sysFile);
        }
    }

    /**
     * 字节转换
     * @param size 字节大小
     * @return 转换后值
     */
    private static String convertFileSize(long size) {
        long kb = 1024;
        long mb = kb * 1024;
        long gb = mb * 1024;
        if (size >= gb) {
            return String.format("%.1f GB", (float) size / gb);
        } else if (size >= mb) {
            float f = (float) size / mb;
            return String.format(f > 100 ? "%.0f MB" : "%.1f MB", f);
        } else if (size >= kb) {
            float f = (float) size / kb;
            return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f);
        } else {
            return String.format("%d B", size);
        }
    }

    private static String formatByte(long byteNumber){
        double FORMAT = 1024.0;
        double kbNumber = byteNumber/FORMAT;
        if(kbNumber<FORMAT){
            return new DecimalFormat("#.##KB").format(kbNumber);
        }
        double mbNumber = kbNumber/FORMAT;
        if(mbNumber<FORMAT){
            return new DecimalFormat("#.##MB").format(mbNumber);
        }
        double gbNumber = mbNumber/FORMAT;
        if(gbNumber<FORMAT){
            return new DecimalFormat("#.##GB").format(gbNumber);
        }
        double tbNumber = gbNumber/FORMAT;
        return new DecimalFormat("#.##TB").format(tbNumber);
    }

    /**
     * JDK启动时间
     */
    private static String getStartTime() {
        long time = ManagementFactory.getRuntimeMXBean().getStartTime();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(new Date(time));
    }

    /**
     * JDK运行时间
     */
    private static String getRunTime() {
        Date nowDate = new Date();
        long time = ManagementFactory.getRuntimeMXBean().getStartTime();
        Date endDate = new Date(time);

        long nd = 1000 * 24 * 60 * 60;
        long nh = 1000 * 60 * 60;
        long nm = 1000 * 60;
        // long ns = 1000;
        // 获得两个时间的毫秒时间差异
        long diff = endDate.getTime() - nowDate.getTime();
        // 计算差多少天
        long day = diff / nd;
        // 计算差多少小时
        long hour = diff % nd / nh;
        // 计算差多少分钟
        long min = diff % nd % nh / nm;
        // 计算差多少秒//输出结果
        // long sec = diff % nd % nh % nm / ns;
        return day + "天" + hour + "小时" + min + "分钟";
    }
}
